#include <cerrno>
#include <cstring>

#include "exception.h"
#include "util.h"
#include "wiki.h"

#define PAR_OP      "<p>"
#define PAR_OP_LEN  3
#define PAR_CL      "</p>"
#define PAR_CL_LEN  4

#define NEW_PAR     "</p><p>"
#define NEW_PAR_LEN 7

#define ASIS_BEG      "<pre>"
#define ASIS_BEG_LEN  5
#define ASIS_END      "</pre>"
#define ASIS_END_LEN  6

#define REF_BEG       "<a href=\""
#define REF_BEG_LEN   9
#define REF_MID       "\">"
#define REF_MID_LEN   2
#define REF_END       "</a>"
#define REF_END_LEN   4

#define ERR1           "<font color=red>"
#define ERR1_LEN       16
#define ERR2           "</font>"
#define ERR2_LEN       7

#define ERR_ADD_CL        "<font color=red>}</font>"
#define ERR_ADD_CL_LEN    24
#define ERR_UNEXP_SL      "<font color=red>\\</font>"
#define ERR_UNEXP_SL_LEN  24
#define ERR_UNEXP_OP      "<font color=red>{</font>"
#define ERR_UNEXP_OP_LEN  24

typedef void (*op_t)(char * cur_page, const char * page_max, const char * & src, const char * src_max);

typedef enum{type1, type2} op_type_t;

static void op_asis(char * cur_page, const char * page_max, const char * & src, const char * src_max) {

		char * page = cur_page + strlen(cur_page);

		if( page + ASIS_BEG_LEN < page_max ) {
				memcpy(page, ASIS_BEG, ASIS_BEG_LEN);
				page += ASIS_BEG_LEN;
		} else 
				goto page_overful;

		while( src < src_max ) {
				{
						size_t len = 0;
						while( src + len < src_max && '\\' != src[len] && 
							   '{' != src[len] && '}' != src[len] ) ++len;
						
						if( page + len < page_max ) {
								memcpy(page, src, len);
								page += len;
								src += len;
						} else
								goto page_overful;

						if( src >= src_max )
								break;
				}

				if( '{' == *src ) {				
						if( page + ERR_UNEXP_OP_LEN < page_max ) {
								memcpy(page, ERR_UNEXP_OP, ERR_UNEXP_OP_LEN);
								page += ERR_UNEXP_OP_LEN;
						} else 
								goto page_overful;
						++src;

                } else if( '}' == *src ) {
						*page = '\0';
						break;

                } else if( '\\' == *src ) {

						++src;

                        if( src < src_max && ('{' == *src || '}' == *src || '\\' == *src) ) {

								if( page + 1 < page_max ) 
										*(page++) = *(src++);
								else
										goto page_overful;
						} else {
								if( page + ERR_UNEXP_SL_LEN < page_max ) {
										memcpy(page, ERR_UNEXP_SL, ERR_UNEXP_SL_LEN);
										page += ERR_UNEXP_SL_LEN;
								} else
										goto page_overful;
						}

				}
		}

		if( page + ASIS_END_LEN < page_max ) {
				memcpy(page, ASIS_END, ASIS_END_LEN);
				page += ASIS_END_LEN;
		} else 
				goto page_overful;

		*page = '\0';

		return;

 page_overful:
		throw std::runtime_error("op_asis()");
}

static void op_ref(char *cur_page, const char *page_max, const char *&src, const char *src_max) {

		char *page = cur_page + strlen(cur_page);

		const char *label = cur_page;
		size_t label_len;
		const char *ref;
		size_t ref_len;
		const char *tmp;
		
		for(--page; page >= label && util_isspace(*page); --page);
		*(++page) = '\0';
		label_len = page - label;
		++page;
		
		ref = page;
		for(; src < src_max && util_isspace(*src); ++src);
		for(; src < src_max && '}' != *src; ++src)
				if( page + 1 < page_max )
						(*page++) = *src;
				else
						goto page_overful;
		for(--page; page >= ref && util_isspace(*page); --page);
		*(++page) = '\0';
		ref_len = page - ref;
		++page;

		tmp = page;
		size_t tmp_len;
		if( 0 == label_len ) {
				if( page + REF_BEG_LEN + ref_len + REF_MID_LEN + ref_len + REF_END_LEN < page_max ) {
						memcpy(page, REF_BEG, REF_BEG_LEN);
						page += REF_BEG_LEN;

						memcpy(page, ref, ref_len);
						page += ref_len;

						memcpy(page, REF_MID, REF_MID_LEN);
						page += REF_MID_LEN;

						memcpy(page, ref, ref_len);
						page += ref_len;

						memcpy(page, REF_END, REF_END_LEN);
						page += REF_END_LEN;
				
						tmp_len = REF_BEG_LEN + ref_len + REF_MID_LEN + ref_len + REF_END_LEN;
				} else
						goto page_overful;
		} else {
				if( page + REF_BEG_LEN + ref_len + REF_MID_LEN + label_len + REF_END_LEN < page_max ) {
						memcpy(page, REF_BEG, REF_BEG_LEN);
						page += REF_BEG_LEN;

						memcpy(page, ref, ref_len);
						page += ref_len;

						memcpy(page, REF_MID, REF_MID_LEN);
						page += REF_MID_LEN;

						memcpy(page, label, label_len);
						page += label_len;

						memcpy(page, REF_END, REF_END_LEN);
						page += REF_END_LEN;

						tmp_len = REF_BEG_LEN + ref_len + REF_MID_LEN + label_len + REF_END_LEN;
				} else
						goto page_overful;
		}

		memmove(cur_page, tmp, tmp_len);

		cur_page[tmp_len] = '\0';

		return;

 page_overful:
		throw std::runtime_error("op_ref()");
}

struct {
		char *name;
		op_type_t type;

		// type1
		char *prefix;
		char *suffix;

		// type2
		op_t op;
} const ops[] = {
		// name		// type		// prefix, suffix			// op
		{"asis",		type2, 		NULL, NULL,				op_asis},
		{"bf",		type1,		"<strong>", "</strong>",	NULL}, 
		{"br",		type1,		"<br/>", NULL, 			NULL},
		{"center", 	type1,		"<center>", "</center>",	NULL},
		{"index",		type1,		"<ol>", "</ol>", 			NULL},
		{"it",			type1,		"<i>", "</i>",				NULL}, 
		{"item",		type1,		"<li>", "</li>",				NULL},
		{"line",		type1,		"<hr/>", NULL,			NULL},
		{"list",		type1,		"<ul>", "</ul>",			NULL},
		{"ref",		type2,		NULL, NULL,				op_ref},
		{"section",	type1,		"<h1>", "</h1>",			NULL}, 
		{"subsection",	type1,		"<h2>", "</h2>",			NULL},
		{"subsubsection", type1,	"<h3>", "</h3>",			NULL},
};

const size_t ops_count = sizeof(ops)/sizeof(ops[0]);

#define PARSE_DEEP_MAX 1024

size_t wiki_parser(const char *src, size_t src_size, char *page, size_t page_size) {
        char * _op_stack_tmp[PARSE_DEEP_MAX];
		const char 
				**op_stack_min = (const char **)_op_stack_tmp, 
				**op_stack_max = (const char **)_op_stack_tmp + PARSE_DEEP_MAX;
		char **op_stack = _op_stack_tmp;

		const char *src_max = src + src_size;

		const char 
				*page_min = page,
				*page_max = page + page_size;

		char *cur_page = page;

		if( page + PAR_OP_LEN < page_max ) {
				memcpy(page, PAR_OP, PAR_OP_LEN);
				page += PAR_OP_LEN;
		} else 
				goto page_overful;
		
        while( src < src_max ) {

				{
						size_t len = 0;
						while( src + len < src_max && !util_isspace(src[len]) && '\\' != src[len] && 
							   '{' != src[len] && '}' != src[len] ) ++len;
							   
						if( page + len < page_max ) {
								memcpy(page, src, len);
								page += len;
								src += len;
						} else
								goto page_overful;
					
						if( src_max == src )
								break;
				}

                if( util_isspace(*src) ) {
						if( page + 1 < page_max )
								*(page++) = ' ';
						else
								goto page_overful;
                        
                        while( src < src_max && util_isspace(*src) && '\n' != *src )
								++src;

                        if( src < src_max && '\n' == *src ) {
								for(++src; src < src_max && util_isspace(*src); ++src) 
										if( '\n' == *src ) {
												if( page + NEW_PAR_LEN < page_max) {
														memcpy(page, NEW_PAR, NEW_PAR_LEN);
														page += NEW_PAR_LEN;
														++src;
														break;
												} else
														goto page_overful;
										}
                        }
                        
                        while( src < src_max && util_isspace(*src) )
								++src;

                } else if( '{' == *src ) {
						if( op_stack_max == op_stack )
								goto op_stack_overful;

						*(op_stack++) = cur_page;
						cur_page = page;

                        ++src;

                } else if( '}' == *src ) {

						char *str = NULL;
						while( op_stack_min != op_stack && 
							   (str = *(--op_stack), str < page_min || page_max <= str) ) {
								
								const size_t str_len = strlen(str);
								if( page + str_len < page_max ) {
										memcpy(page, str, str_len);
										page += str_len;
								} else
										goto page_overful;
						}

						if( page_min <= str && str < page_max ) 
								cur_page = *op_stack;
						else
								if( page + ERR_ADD_CL_LEN < page_max ) {
										memcpy(page, ERR_ADD_CL, ERR_ADD_CL_LEN);
										page += ERR_ADD_CL_LEN;
								} else
										goto page_overful;

						++src;

				} else if( '\\' == *src ) {

						++src;

						if( src_max == src ) {
								if( page + ERR_UNEXP_SL_LEN < page_max ) {
										memcpy(page, ERR_UNEXP_SL, ERR_UNEXP_SL_LEN);
										page += ERR_UNEXP_SL_LEN;
								} else
										goto page_overful;
								break;
						}

                        if( '{' == *src || '}' == *src || '\\' == *src ) {

								if( page + 1 < page_max ) 
										*(page++) = *(src++);
								else
										goto page_overful;

                        } else {
								size_t len, step;
                        
								for(step = 0; src + step < src_max && util_isalpha(src[step]); ++step);
								len = step;
								if( src + step < src_max && util_isspace(src[step]) ) 
										++step;
								
								size_t i;
                                for(i = 0; i < ops_count; ++i)
										if( memcmp(src, ops[i].name, len) <= 0 )
											break;

                                if( i == ops_count || 
									len != strlen(ops[i].name) ||
									0 != strncmp(src, ops[i].name, len) 
									) {
										if( page + ERR1_LEN + len + 1 + ERR2_LEN < page_max ) {
												memcpy(page, ERR1, ERR1_LEN); page += ERR1_LEN;
												*(page++) = '\\';
												memcpy(page, src, len); page += len;
												memcpy(page, ERR2, ERR2_LEN); page += ERR2_LEN;
										} else 
												goto page_overful;

										src += step;
								
                                } else if( type1 == ops[i].type ) {
										src += step;

										if( NULL != ops[i].prefix ) {
												const size_t prefix_len = strlen(ops[i].prefix);
												if( page + prefix_len < page_max ) {
														memcpy(page, ops[i].prefix, prefix_len);
														page += prefix_len;
												} else
														goto page_overful;
										}

										if( NULL != ops[i].suffix ) {
												if( op_stack_max == op_stack )
														goto op_stack_overful;
												*(op_stack++) = ops[i].suffix;
										}

								} else {
										src += step;

										*page = '\0';
										(*ops[i].op)(cur_page, page_max, src, src_max);
										page = cur_page + strlen(cur_page);
								}


						}

				}
		}

		while( op_stack > op_stack_min ) {
				const char *str = *(--op_stack);
				if( page_min <= str && str < page_max ) {
						if( page + ERR_ADD_CL_LEN < page_max ) {
								memcpy(page, ERR_ADD_CL, ERR_ADD_CL_LEN);
								page += ERR_ADD_CL_LEN;
						} else
								goto page_overful;

				} else {
						const size_t str_len = strlen(str);
						if( page + str_len < page_max ) {
								memcpy(page, str, str_len);
								page += str_len;
						} else
								goto page_overful;
				}
		}
        
		if( page + PAR_CL_LEN < page_max ) {
				memcpy(page, PAR_CL, PAR_CL_LEN);
				page += PAR_CL_LEN;
		} else 
				goto page_overful;
		
		return page - page_min;

 page_overful:
		throw std::runtime_error("wiki_parser()");

 op_stack_overful:
		throw std::runtime_error("wiki_parser()");
}

